#pragma once

#include "logMessage.hpp"
#include "thread.hpp"
#include "threadpool.hpp"
#include "task.hpp"
#include <iostream>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>          
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>

using namespace std;

enum
{
    USAGE_ERR = 1,
    SOCKET_ERR,
    BIND_ERR,
    LISTEN_ERR
};

const int gport = 1024;
const int gbacklog = 5;

struct ThreadData
{
    int sockfd;
    struct sockaddr_in peer;
};

void serverIO(int sockfd, struct sockaddr_in& peer)
{
    while (true)
    {
        char buffer[1024];
        int n = read(sockfd, buffer, sizeof buffer - 1);
        if (n == 0) // 没有读取到任何数据 表示客户端已经退出
        {
            logMessage(NORMAL, "client quit");
            break;
        }
        if (n > 0)
        {
            buffer[n] = 0;
            logMessage(NORMAL, "%s %s", inet_ntoa(peer.sin_addr), buffer);
            // 向客户端返回处理结果
            string response(buffer);
            response += " [server echo]";
            write(sockfd, response.c_str(), response.size());
        }
    }
}

//version3
void* start_routine(void* args)
{
    pthread_detach(pthread_self());//与主线程分离 主线程无需等待join子线程 子线程由os回收
    ThreadData* td = (ThreadData*)args;
    serverIO(td->sockfd, td->peer);
    close(td->sockfd);
    delete td;
    return nullptr;
}

class tcpServer
{
public:
    tcpServer(uint16_t port)
    : _listenSockfd(-1),
      _port(port)
    {}

    void initServer()
    {
        //忽略进程等待信号 将子进程交给os回收
        //signal(SIGCHLD,SIG_IGN);
        //创建socket文件
        _listenSockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listenSockfd == -1)
        {
            logMessage(FATAL, "create socket failed");
            exit(SOCKET_ERR);
        }
        logMessage(NORMAL, "create socket success");
        //绑定ip和port(将当前机器的网络信息写入创建的socket文件中)
        struct sockaddr_in server;
        memset(&server, 0, sizeof(server));
        server.sin_family = AF_INET;
        server.sin_addr.s_addr = INADDR_ANY;//绑定任意ip 云服务器对应不止一个ip 需要保证向所有对应ip发送消息都能收到
        server.sin_port = htons(_port);//主机序列转网络序列
        if (bind(_listenSockfd, (struct sockaddr*)&server, sizeof server) != 0)
        {
            logMessage(FATAL, "server bind failed");
            exit(BIND_ERR);
        }
        logMessage(NORMAL, "server bind success");
        //设置socket为监听状态
        if (listen(_listenSockfd, gbacklog) != 0)
        {
            logMessage(FATAL, "server listen failed");
            exit(LISTEN_ERR);
        } 
        logMessage(NORMAL, "server listen success");
    }

    void start()
    {
        //实例化单例线程池
        /*
        不能在initServer中实例化 因为守护进程化后的进程为子进程 而父进程已经退出
        线程池中的线程取IO任务需要在父进程创建的条件变量上等待
        服务器(子进程)获取新链接后push任务到队列 唤醒线程池中的线程是去子进程的条件变量中唤醒
        因此唤醒不了等待中的线程 导致无法处理IO任务和文件描述符泄露等问题
        */
        threadPool<Task>::getInstance();
        while (true)
        {
            // 获取链接
            struct sockaddr_in peer;
            socklen_t len = sizeof peer;
            // 阻塞式接收链接
            //如果在父进程/主线程阻塞接收链接时 子进程/从线程释放之前的sockfd 父进程/主线程在收到新链接后不会立刻使用原先的fd而会等下次再用
            int sockfd = accept(_listenSockfd, (struct sockaddr *)&peer, &len); // 用于C/S通信的socket文件
            if (sockfd == -1)
            {
                logMessage(ERROR, "accept failed");
                continue;
            }
            logMessage(NORMAL, "accept success, sockfd:%d", sockfd);

            //接收客户端数据并返回处理结果
            //version1 单进程版 该版本中服务器只能同时与一个客户端进行通信
            // serverIO(sockfd, peer);
            // close(sockfd);//当前通信的客户端关闭 用于通信的socket文件也需要关闭

            //version2 多进程版
            //2(1)信号忽略
            // pid_t id = fork();
            // if (id == 0)//子进程
            // {
            //     //子进程有独立的文件描述符表 但文件描述符指向的文件结构体与父进程相同
            //     close(_listenSockfd);//子进程关闭监听sock 防止非法的读写操作 底层的引用计数-1 不会释放文件结构体
            //     serverIO(sockfd, peer);
            //     close(sockfd);//客户端退出后 子进程关闭用于通信的socket文件
            //     exit(0);
            // }
            // close(sockfd);//子进程正在处理服务器数据的读写操作 父进程关闭文件描述符只是减掉引用计数 不会释放文件结构体
            //2(2)孙子进程 对比信号忽略版 创建子进程的次数更多 消耗更大
            // pid_t id = fork();
            // if (id == 0)
            // {
            //     close(_listenSockfd);
            //     if (fork() > 0) exit(0);
            //     //子进程退出 以下是孙子进程 由于孙子进程没有父进程 所以会当作孤儿进程被os回收 不需要做进程等待
            //     serverIO(sockfd, peer);
            //     close(sockfd);
            //     exit(0);
            // }
            // close(sockfd);

            //version3 多线程版
            // pthread_t tid;
            // ThreadData* td = new ThreadData;//主线程离开当前作用域后栈空间销毁 所以td需要创建在堆上
            // td->sockfd = sockfd;
            // td->peer = peer;
            // pthread_create(&tid, nullptr, start_routine, td);//子进程处理服务器数据的读写

            //version4 线程池版
            Task t(sockfd, peer, serverIO);
            threadPool<Task>::getInstance()->push(t);
        }
    }

private:
    int _listenSockfd;
    uint16_t _port;
};